Слой (layer), называемый Моделью (Model), в объектно-ориентированном приложении содержит бизнес-логику. Которая обычно состоит из структур данных, вычислительной или прикладной логики, проверок и прочего, ассоциированных с данными.
OpenXava - это ориентированный на модель фреймворк, в котором модель имеет наибольшее значение, и все остальное (например, пользовательский интерфейс) зависит от неё или является её производным.
В последних версиях OpenXava для специфицирования модели используются Plain Old Java Objects (POJO) - простые Java классы + используются аннотации, которые стали доступны в Java5. Также возможно определение модели с помощью XML, которое было реализованно в более раних версиях OX.
Из описанной вами модели OpenXava способен создавать полностью функциональные веб приложения.
Бизнес-компонент (Business Component)
Фундаментом OpenXava является понятие бизнес-компонента (business component). Бизнес-компонент опреляется с помощью Java объекта, именнуемого в OX сущностью (Entity). Данный класс является обычным EJB3 компонентом, или другими словами, классом POJO с аннотациями, реализованными в соответствии со стандартом Java Persistence API (JPA).
JPA - это стандарт Java для постоянного хранения объектов (persistence). В первую очередь он предназначен для сохранения и извлечения состояния объектов Java в/из базы данных. Если вы имете опыт разработки JPA с использованием POJO-объектов, считайте, что вы уже умеете разрабатывать приложения OpenXava.
Давайте поймем, что мы можем реализовать, определив Бизнес-компонент, который на самом деле является простым Java классом:
Model (Модель): Структуры данных, проверки, вспомогательные вычисление и многое другое.
View(Представление): Это, как модель будет отображаться пользователю.
Tabular data(Табличное представление или списки объектов): Специальное представление для презентации списка объектов в режиме списка (по-другому, таблицы).
Object/relational mapping(Правила сохранения объектов в базу данных): Описывает, как будет сохраняться и извлекаться состояние объектов в/из базы данных.
Данная глава посвещена определению модели данных.
Сущность (Entity)
Для того, чтобы определить модель, нужно начать с создания обычного класса Java. Дополнительная информация, необходимая для OX, определяется с помощью аннотаций. OpenXava поддерживает аннотации JPA и Hibernate Validator. Кроме этого, OpenXava определяет целый ряд собственных аннотаций для управления визуализацией бизнес-компонентов и другой функциональностью.
Аббревиатура JPA в тексте данной главы указывает аннонтации Java Persistent API, аббревиатурой HV обозначены аннотации Hibernate Validator, а аббревиатура OX указывает аннотации OpenXava.
Синтаксическое определение entity следующее:
@Entity (JPA, одно вхождение, обязательный элемент): Указывает, что данный класс является сущностью JPA, другими словами, экземпляры данного класса управляются системой постоянного хранения объектов (persistent objects).
@EntityValidator (OX, несколько вхождений, необязательный элемент): Выполяет проверку на уровене модели. В данной аннотации можно задать значение любых нескольких свойств модели. Для проверки значения одного свойства мы рекомендуем использовать проверку на уровне конркетного свойства (property level validator).
@RemoveValidator (OX, несколько вхождений, необязательный элемент): Данная проверка выполняется перед удалением объекта. Позволяет запретить удаление объекта.
Class declaration: Обычное определение класса Java. Кроме прочего, можно использовать наследование других классов и реализацию интерфейсов (служебные слова Java extends и implements).
Properties: Обычные свойства (private переменные-члены класса, имеющие Get/Set методы) Java. Значения свойств определяет состояние объекта.
Methods: Обычные методы Java класса, могут содержать бизнес-логику (business logic).
Finders: Методы выборки объектов из базы данных(Finder). Это статические методы (static), позволяющие реализовать сложный поиск объектов в БД, используя рассширенные возможности JPA.
Callback methods: Методы обратного вызова JPA (JPA callbacks methods) для добавления собственной логики, которая будет задествована при вставке, изменении или удалении объектов в базе данных.
Свойства сущности (Properties)
Свойство представляет собой один отдельный аттрибут сущности. В OX свойство реализуется как переменная-член класса Java. От обычной переменной члена класса свойство отличается тем, что свойство обычно имеет область видимости private, а также реализованы методы get/set (которые называются геттеры и сеттеры на программистском сленге) для чтения и установки значения свойства. Например, свойство X будет иметь методы setX и getX. Иногда свойство имеет только один из методов, например, если это вычислимое свойство, но сейчас мы не будем рассматривать такой случай.
Синтаксическое определение свойства в OX следующее:
@Stereotype (OX, необязательный элемент): OX уже имеет готовые стереотипы для некоторых распространённых свойств, например, адрес e-mail. С помощью данной аннотации вы можете указать такой готовый стеретип.
@Column(length=) (JPA), @Max (HV), @Length(max=) (HV), @Digits(integerDigits=) (HV, необязательный элемент, предполагается, что одновременно будет использоваться только одна из приведённых аннотаций): Аттрибуты length= или max= определяет длину значения свойства в символах. В случае аннотации @Maxmax= определяет максимальное значение свойства. Применяется для уточнения отображения свойства в пользовательском интерфейсе. Если вы не задаете размер или максимальное значение, используется значение по-умолчанию, определенное для типа или стереотипа в файле default-size.xml конфигурации.
@Digits(fractionalDigits=) (HV, необязательный элемент): Размер дробной части числа. Применимо только для числовых свойств. Если не задается, используется размер по-умолчанию из файла default-size.xml конфигураци.
@Required (OX), @Min (HV), @Range(min=) (HV), @Length(min=) (HV) (Необязательный элемент. Предполагается, что одновременно будет использоваться только одна из преведённых аннотаций): Указывает, что данное свойство является обязательным. В случае @Min (минимальное значение), @Range (диапазон) или @Length (длина) необходимо указать значение больше 0 для аттрибута min, если необходимо, чтобы значение свойства было задано пользователем. По-умолчанию, ключевые свойства (key properties) являются обязательными (required) и скрытыми (hidden)(начиная с версии v2.1.3). Остальные свойства, по-умолчанию, наоборот, необязательны и видимы. При вставке данных в БД OpenXava проверяет - все ли обязательные свойства в классе присутствуют. При этом, если хотя бы одно из обязательных свойств не заполнено, то сохранение не выполняется и пользователю выводится сообщение об ошибке (validation error list). Логика определения "присутствия" значения свойства (т.е. заполнено свойство или нет) может быть переопределена созданием файла validators.xml внутри проекта. В качестве примера и ознакомления с форматом файла посмотрите основной файл валидации OpenXava/xava/validators.xml. Обращаем ваше внимание, что @Required - это констрейн Hibernate (Hibernate Validator constraint)(начиная с версии v3.0.1).
@Id (JPA, необязательный элемент): Указывает, что данное свойство является частью ключа (являлось ключевым свойством - key property). Требуется, чтобы сущность имела хотя бы одно ключевое свойство (или ключом может являтся ссылка (reference)). Комбинация ключевых свойств (key properties) и ключевых ссылок (key references) должна быть спроецированна (mapping) на группу столбцов в таблице(ах) базы данных, которые не могут иметь дуплицированных значения. Обычно, это первичный ключ (primary key) таблицы.
@Hidden (OX, необязательный элемент): Указав, что свойство скрытое, вы исключаете его из автоматической генерации пользовательского интерфейса. Естественно, что java код никуда не пропадает, и вы можете использовать данной свойство программно. Также, если вы явно включите свойство в представление, то оно будет отображено в данном представлении.
@SearchKey (OX, необязательный элемент): Свойства с аттрибутом "Поисковый ключ (search key)" пользователям для поиска. В пользовательском интерфейсе OX предусмотрены специальные редактируемые поля для ввода значения, которое будет использоваться для поиска. По-умолчанию OpenXava использует для поиска ключевые свойства (с аттрибутом @Id). Если же ключевые свойства скрыты (hidden), то ,OpenXava использует первое свойство в представлении (view). Применив @SearchKey, вы точно указываете свойства, которые будут использоваться для поиска.
@Version (JPA, необязательный элемент): Свойство с аттибутом "version" используется логикой оптимистического управления транзакциями (optimistic concurrency control). Если вы хотите активировать функцию "control concurrency" - все,что нужно, иметь свойство с аттрибутом @Version для данной сущности. В одной сущности может быть использовано только одно свойство с аттрибутом @Version. Разрешенные типы для использования с аттрибутом @Version: int, Integer, short, Short, long, Long, Timestamp. Такие свойства также являются скрытыми (см. @hidden).
@DefaultValueCalculator (OX, одно вхождение, необязательный элемент): Реализует логику вычисления начального (default) значения для свойства. OpenXava требует, чтобы свойство с аттрибутом @DefaultValueCalculator имело метод для установки значения (setter).
@PropertyValidator (OX, возможно несколько вхождений, необязательный элемент): Определяет логику проверки значения свойства (валидации), которая выполняется перед изменением или созданием объекта, который содержит данное свойство.
Определение свойства (Property declaration): Обычное для Java определение свойства с get- и set- методами для доступа к значению свойства (getters и setters). Если вы хотите создать вычислимое свойство (calculated property), то для это нужно только определить get- метод без собственно определения переменной класса и set- метода. В качестве типа данных можно использовать любой разрешенный в JPA тип.
Стереотип (Stereotype)
Стереотип (@Stereotype) позволяет упростить определение и сопровождение сложного поведения свойств. В большом приложении обычно во многих сущностях используются похожие или идентичные свойства. Стереотип позволяет избежать необходимость описывать / реализовывать все аспекты поведения и логики для каждого отдельного свойства. Все похожие свойства могут быть реализованы на базе одного стереотипа. Разработчик может создать свой или задействовать уже реализованный стереотип. OX уже имеет готовые стереотипы для некоторых распространённых случаев, например: адрес e-mail, название (name), комментарий (comment), описание (description) и т.д. OX реализует поведение по-умолчанию для большинства стандартных типов Java, которые поддерживаются JPA. Но если вы хотите построить действительно профессиональный пользовательский интерфейс - например, настроить проверку корректности значений (validators), размеры по-умолчанию (default sizes), редактор (visual editors) и многое другое специальным образом для конкретных свойств, то сделать это пожет использование стереотипов. Ничто не мешает вам создать собственные стереотипы, например, NAME, MEMO или DESCRIPTION и использовать их для ваших свойств.
Ниже приведен список стереотипов, входящих в стандартную поставку OpenXava:
RELLENADO_CON_CEROS, ZEROS_FILLED - поле с заполнением нулями
TEXTO_HTML, HTML_TEXT (text with editable format) - текст
IMAGE_LABEL, ETIQUETA_IMAGEN - изображение, зависящее от значения свойства
EMAIL - адрес электронной почты
TELEFONO, TELEPHONE - телефон
WEBURL - веб URL
IP - ip адрес
ISBN - International Serial Book Number
TARJETA_CREDITO, CREDIT_CARD - номер кредитной карты
LISTA_EMAIL, EMAIL_LIST - список адресов электронной почты
Давайте попробуем создать свой собственный стереотип. Предположим, что вы хотите создать новый стереотип PERSON_NAME, который предназначен для представления Ф.И.О. человека.
Откройте на редактирование (если не существует, то создайте) файл editors.xml в папке xava ваше проекта. Добавьте в файл:
Этим вы определили редактор, который будет использоваться для отображения и редактирования свойств, относящихся к стереотипу PERSON_NAME.
Также вы можете определить размер для свойств, использующих данный стереотип. Делается это в файле default-size.xml вашего проекта:
<for-stereotypename="PERSON_NAME"size="40"/>
В этом примере для свойств со стереотипом PERSON_NAME будет использоваться размер 40, если иного не определено в аттрибутах самого свойства.
Иногда требуется изменить правила валидации (validator) для стереотипа. Для этого необходимо в файл validators.xml вашего проекта добавить следующее:
В этом примере для стереотипа PERSON_NAME в качестве валидатора указан класс NotBlankCharacterValidator из пакета org.xava.validators.
Теперь все готово, чтобы определить свойство с использованием стереотипа PERSON_NAME:
В нашем случае значение 40 задает размер, String - это тип и проверка NotBlankCharacterValidator выполняется, если данное свойство является обязательным.
OX имеет встроенную поддержку галерей изображений. Для создания галереи изображений вам необходимо создать свойство сущности, используя стереотип IMAGES_GALLERY stereotype. Пример ниже:
Столбец photos в базе данных должен иметь тип String и размер 32 символа (VARCHAR(32)).
Мы закончили определение свойства, использующего стереотип IMAGES_GALLERY.
Для того, чтобы создать работающее приложение, нам понадобиться выполнить еще несколько простых шагов.
Первое, что нужно сделать, - это создать таблицу в базе данных для хранения изображений:
CREATETABLE IMAGES (
ID VARCHAR(32)NOTNULLPRIMARYKEY,
GALLERY VARCHAR(32)NOTNULL,
IMAGE BLOB);
CREATEINDEX IMAGES01
ON IMAGES (GALLERY);
Тип BLOB поддерживают не все базы данных. Поэтому в качестве типа данных для столбца IMAGE вам необходимо выбрать тип данных, наиболее подходящий для хранения массива байтов (byte []).
Далее нужно внести изменения в persistence/hibernate.cfg.xml вашего проекта:
Теперь вы можете использовать стереотип IMAGES_GALLERY в вашем приложении.
Параллельное исполнение запросов и свойство Version (Concurrency and version property)
Под параллельным исполнением запросов подразумевается возможность приложения обеспечивать нескольким пользователям одновременно изменять данные. OpenXava использует оптимистическую модель управления транзакциями JPA. При использовании такой модели записи в базе данных не блокируются, что позволяет получить парарлельную работу пользователей без потери целостности данных.
Например, пусть пользователь A прочитал запись. После этого пользователь B прочитал ту же запись, модифицировал ее и сохранил изменения в БД. В том случае, если пользователь A попытается сохранить свои изменения, он получит сообщение об ошибке и будет вынужден заново загрузить данные и повторить свои изменения.
Чтобы задействовать оптимистическую модель управления транзакциями, вам необходимо определить свойство с аттрибутом @Version в вашей сущности. Пример определения такого свойства:
@Version
privateint version;
Данное свойство предназначено для использования JPA, поэтому ваше приложение или пользователи не должны использовать данное свойство явным способом.
Перечислимые типы (Enums)
OpenXava поддерживает введённые в Java 5 перечислимые типы enums. Используя enum, вы можете определить свойство, которое может принимать только значения из фиксированного набора, заданного при объявлении enum.
Пример:
private Distance distance;publicenum Distance { LOCAL, NATIONAL, INTERNATIONAL };
В данном примере свойство distance может принимать только следующие значения: LOCAL, NATIONAL or INTERNATIONAL, и если вы не указали аттрибут @Required, то значение null также допустимо.
В текущей реализации пользовательского интерфейса такое свойство представляется в виде combo. Текст метки для каждого значения берётся из файлов i18n.
В базе данных по умолчанию значение сохраняется как integer (в нашем примере, 0 - это LOCAL, 1 - NATIONAL, 2 - INTERNATIONAL и null, если значение не определено). Тем не менее, вы можете использовать другой тип, что удобно, если вы работаете с уже существующей базой данных. Более подробная информация дана в другой главе.
Вычисляемые свойства (Calculated properties)
Вычислимые свойства не могут быть изменены явным способом (т.к. имеют только метод установки значения getter) и не сохраняются в базе данных (соответственно, не подлежат проецированию на столбцы в БД).
Ниже приведен пример определения вычислимого свойства:
Использовать выше представленное определение можно так:
Product product = ...
product.setUnitPrice(2);BigDecimal result = product.getUnitPriceInPesetas();
Result будет равен 332.772.
Свойство unitPriceInPesetas будет отображаться пользователю нередактируемым. Визуальная длина данного поля будет 10, что определяется путем задания аттрибута @Max(9999999999L) (2). Т.к. мы указали аттрибут @Depends("unitPrice") (1), то если пользователь изменит значение свойства unitPrice в пользовательском интерфейсе, то значение свойства unitPriceInPesetas будет пересчитано и новое значение будет отображено пользователю.
В getter-методе вычислимого свойства вы можете возможности JDBC, как показано в этом примере:
@Max(999)publicint getDetailsCount(){// An example of using JDBCConnection con = null;try{
con = DataSourceConnectionProvider.getByComponent("Invoice").getConnection();// 1String table = MetaModel.get("InvoiceDetail").getMapping().getTable();PreparedStatement ps = con.prepareStatement("select count(*) from " + table +
" where INVOICE_YEAR = ? and INVOICE_NUMBER = ?");
ps.setInt(1, getYear());
ps.setInt(2, getNumber());ResultSet rs = ps.executeQuery();
rs.next();Integer result = newInteger(rs.getInt(1));
ps.close();return result;}catch(Exception ex){
log.error("Problem calculating details count of an Invoice", ex);// You can throw any runtime exception herethrownewSystemException(ex);}finally{try{
con.close();}catch(Exception ex){}}}
Прямое использование JDBC выглядит не очень модно, но позволяет улучшить производительность приложения. Класс DataSourceConnectionProvider дает возможность получить соединение JDBC, ассоциированное с источником данных для указанной сущности (в нашем случае это Invoice). Данный класс был добавлен в OX для удобства разработки, но вы можете подключаться к JDBC с использованием JNDI или любым иным удобным вам способом. На самом деле вы можете использовать любой код Java для расчета значения вычислимого свойства.
Если вы используете доступ на базе свойств (property-based access),то вам необходимо добавить аннотацию @Transient для вычислимого свойства. Пример:
privatelong number;
@Id @Column(length=10)// You annotated the getter,publiclong getNumber(){// so JPA will use property-base access for your classreturn number;}publicvoid setNumber(long number){this.number = number;}
@Transient // You have to annotate as Transient your calculated propertypublicString getZoneOne(){// because you are using property-based accessreturn"In ZONE 1";}
Вычислитель исходного значения (Default value calculator)
Используя аттрибут @DefaultValueCalculator, вы можете задать для свойства класс, который будет определять исходное значение для данного свойства. Такой класс в OX называется калькулятором или вычислителем исходного значения. Например:
В этом случае, когда пользователь попытается создать новый экземпляр сущности, он увидит, что значение для свойства "year" уже задано. Далее, если это необходимо, пользователь может изменить данное значение. Логика для вычисления исходного значения определена в классе CurrentYearCalculator:
packageorg.openxava.calculators;importjava.util.*;/**
* @author Javier Paniza
*/publicclass CurrentYearCalculator implements ICalculator {publicObject calculate()throwsException{Calendar cal = Calendar.getInstance();
cal.setTime(new java.util.Date());returnnewInteger(cal.get(Calendar.YEAR));}}
Существуют и другие варианты использования @DefaultValueCalculator:
В данном случае для вычисления исходного значения OpenXava создает экземпляр StringCalculator, устанавливает значение "GOOD" для свойства string экземпляра StringCalculator, и в конце вызывает его метод calculate() для вычисления значения свойства relationWithSeller. Как вы увидели, используя аннотацию @PropertyValue, вы можете создавать калькуляторы, подходящие для многократного использования.
Анотация @PropertyValue также позволяет использовать значения свойств других сущностей, например:
В данном случае OpenXava заполняет свойство drivingLicenceType посредством использования класса CarrierRemarksCalculator значением отображаемого свойства type из ссылочной сущности drivingLicence. Обратите внимание, что аттрибут from поддерживает ссылки на свойства (reference.property).
Также можно использовать @PropertyValue без from или value:
В данном случае OpenXava использует значение отображаемого свойства familyNumber и вставляет значение в свойство familyNumber калькулятора. Выражение @PropertyValue(name="familiyNumber") эквивалентно @PropertyValue(name="familiyNumber", from="familyNumber").
Внутри класса-калькулятора вы можете использовать возможности JDBC напрямую. Вот пример:
Чтобы использовать прямые вызове JDBC в калькуляторе, класс должен реализовывать интерфейс IJDBCCalculator (1), включая определение переменную-член класса IConnectionProvider (2).
Уже реализованные калькуляторы вы найдете в пакете org.openxava.calculators из поставки OpenXava.
Исходные значения при создании (Default values on create)
Вы можете определить значение свойства, которое будет вычисленно и использоваться только при создании экземпляра сущности (при вставке записи в БД).
Значения свойств первичного ключа обычно вычисляются с применением JPA.
Например, автоикрементное значения для первичного ключа реализуется так:
Более подробную информацию об аннотации @GeneratedValues вы найдете в главе 9.1.9 спецификации стандарта JPA 1.0 (часть JSR-220).
Если вам необходимо реализовать нестандартную логику для вычисления значения ключевого свойства при создании объекта или вам нужно генерировать значения неключевого свойства, то вместо @GeneratedValue, вы вам придется использовать следующую конструкцию:
Анотация @PrePersist (JPA) определяет метод, который будет выполнен перед первой вставкой данных в БД. В данном методе вы можете реализовать любую самую экзотическую логику для вычисления значения любых свойств: и ключевых, и неключевых.
Проверка значения свойства (Property validator)
Анонтация @PropertyValidator позволяет указать класс (назовём его валидатором), который будет выполнять логику проверки значения свойства перед тем, как данное значение присвоить свойству. Свойство может одновременно иметь несколько проверок и, соответственно, конструкций @PropertyValidator:
Техника конфигурирования валидатора примерно такая же как для калькулятора. За исключением того, что в аннотации @PropertyValue нет аттрибута from и вам нужно всегда использовать аттрибут value.
Указав аттрибут onlyOnCreate=”true”, вы создадите валидацию, которая будет выполняться только при создании объекта и никогда при модификации.
Пример класса-валидатора:
Давайте рассмотри валидатор подробнее. Валидатор должен реализовывать интерфейс IPropertyValidator (1), что вынуждает класс иметь метод validate(), где, собственно, и происходит проверка значения.
Параметры метода validate() следующие:
Messages errors: Объек типа Messages, который представляет собой набор сообщений об ошибках, возникших при валидации. При создании своего валидатора вам необходимо добавлять сюда сообщения обо всех ошибках валидации, о которых вы хотите сообщить пользователю.
Object value: Значение, которое мы будет проверять.
String objectName: Имя объекта, к которому относится свойство, которое мы проверяем. Полезно для вывода в сообщения об ошибках.
String propertyName: Имя свойства для валидации. Полезно для вывода в сообщения об ошибках.
Итак, если вы нашли ошибку (значение не прошло одну из проверок), вам необходимо сообщить об этом, добавив сообщение вызовом метода errors.add()). Для вызова данного метода необходимо задать идентификатор и агрументы сообщения. Идентификатор сообщения указывает на сообщение, определенное в ресурсном файле i18n вашего проекта. Вот пример определения сообщения:
exclude_string={0} cannot contain {2} in {1}
Параметры {0}..{2} подменяются на аргументы, которые вы передаете в вызове errors.add().
Если идентфикатор сообщения не найдет в ресурсном файле, то вместо текста сообщения будет отображен его идентификатор.
Проверка значения (валидация) считается успешной, если ни одного сообщения не было добавлено в Messages errors. OpenXava собирает в единый список все сообщения от всех валидаторов перед сохранением объекта, и если список сообщений не пуст, то отображает данные сообщения и не позволяет пользователю сохранить объект.
В пакете org.openxava.validators вы найдете уже реализованные валидаторы.
Напомним, что @PropertyValidator определен как Hibernate Validator contraint(начиная с версии v3.0.1).
Проверка значения свойства для стереотипа или типа (Default validator) (начиная с версии v2.0.3)
Вы можете определить валидатор значения свойства на уровне типа или стереотипа свойства. Для этого необходимо в файле xava/validators.xml вашего проекта определить соответствующий валидатор.
К примеру, добавьте в файл xava/validators.xml вашего проекта следующее:
Значение данного свойства будет проверяться валидатором PersonNameValidator, несмотря на то, что само свойство не определяет никаких валидаторов. Валидатор PersonNameValidator будет применяться для всех свойств со стереотипом PERSON_NAME.
Примерно также вы можете создать валидатор для типа.
В файле validators.xml также можно определять валидаторы, выполняющиеся при проверке, представлено ли значение для обязательного свойства (аннотация @Required).
Также можно назначать имена (alias - алиасы) для классов-валидаторов.
Больше информации о валидаторах вы можете почерпнуть в файлах OpenXava/xava/validators.xml и OpenXavaTest/xava/validators.xml.
Обращаем ваше внимание, что валидаторы не сработают, если вы используете напрямую JPA api для сохранения ваших объектов в БД.
Ссылки (References)
Ссылки позволяют описать связь между сущностями. Ссылка реализуется как свойство (имеющее getter- и setter-методы), при этом тип данного свойства должен соответствовать одной из сущностей-моделей OX. Например, Customer может ссылаться на Seller, что позволит вам написать примерно такой код:
Метод getName() в примере возвращает имя покупателя.
Синтаксическое определение ссылки таково:
@Required // 1
@Id // 2
@SearchKey // 3 New in v3.0.2
@DefaultValueCalculator // 4
@ManyToOne(// 5
optional=false// 1)private type referenceName;// 5public type getReferenceName(){ ... }// 5publicvoid setReferenceName(type newValue){ ... }// 5
@ManyToOne(optional=false) (JPA), @Required (OX) (необязательная аннотация, рекомендуем использовать вариант JPA): Указывает, что ссылка является обязательной. В момент сохранения OpenXava проверяет, все ли обязательные свойства-ссылки заполнены. Если данное требование не выполнено, то сохранение прерывается и пользователю выводится список сообщений об ошибках.
@Id (JPA, небязательная аннотация): Указывает, что свойсво-ссылка является частью ключа. Комбинация ключевых свойств и ссылочных свойств должна быть спроецирована на группу столбцов базы данных с уникальной кобинацией значений, обычно, это первичный ключ.
@SearchKey (OX, необязательная аннотация): (Начиная с версии v3.0.2) Ключ поиска используется для поиска объектов пользователем в пользовательском интерфейсе. Редактируемые поля, ассоцириуемые с каждым свойством, доступным для поиска, в пользовательском интерфейсе позволяют вводить значения для поиска (фильтрации). По-умолчанию для реализации поиска OpenXava использует свойства, анотированные. Если же данные свойства скрыты, используется первое свойство в представлении. Анотация @SearchKey позволяет точно настоить поиск в пользовательском интерфейсе.
@DefaultValueCalculator (OX, допускается только одно вхождение аннотации, необязательная аннотация): Реализует логику вычисления исходного (начального) значения свойства-ссылки. Класс-калькулятор, реализующий вычисление исходного значения, должен возвращать действительное значение, соответствующее реально существующему первичном ключу сущности, на которую реализуется ссылка. При этом, значение может быть простого типа, а может быть сложным объектом-ключом (если ключ реализуется как отдельный объект).
Reference declaration (Определение свойства-ссылки): Обычное определение свойства на языке Java (закрытая (private) переменнная-член класса и getter- и setter-методы). Свойство-ссылка всегда имеет аннотацию @ManyToOne (JPA) и тип свойства всегда соответствует другой сущности.
Свойство-ссылка seller ссылается на сущность Seller.
Свойство-ссылка alternateSeller также реализует ссылку на сущность Seller. В данном случае вы указываем режим выборки fetch=FetchType.LAZY, что означает, что данные из базы данных будут загружаться только по мере необходимости (когда к ним происходит обращение). Это более эффективный подход с точки зрения производительности, чем поведение JPA по-умолчанию. Мы советуем всегда использовать fetch=FetchType.LAZY при определении свойств-ссылок.
Если предположить, что выше приведённое определение ссылок-свойств было выполнено для сущности Customer, то можно написать следюущий код, использующий данные свойства:
Вычислитель исходного значения для ссылки (Default value calculator in references)
Для свойства-ссылки аннотация @DefaultValueCalculator работает совершенно также как для обычного свойства с той только разницей, что должно возвращаться значение, соответствующее реальной ссылке.
Для простого ключа определение калькулятора исходного значения может быть следующим:
@ManyToOne(optional=false, fetch=FetchType.LAZY) @JoinColumn(name="FAMILY")
@DefaultValueCalculator(value=IntegerCalculator.class, properties=
@PropertyValue(name="value", value="2"))private Family family;
Из примера видно, что возвращается значение integer. Для свойства family будет возвращаться значение 2, определенное как @PropertyValue(name="value", value="2").
Определение сложного ключа (composed key) для сущности:
А теперь рассмотрим код калькулятора, который работает с композитным ключом:
packageorg.openxava.test.calculators;importorg.openxava.calculators.*;/**
* @author Javier Paniza
*/publicclass DefaultWarehouseCalculator implements ICalculator {publicObject calculate()throwsException{
WarehouseKey key = new WarehouseKey();
key.setNumber(4);
key.setZoneNumber(4);return key;}}
Класс возращает объект типа WarehouseKey.
Использование ссылок как ключей (Using references as key)
Свойство-ссылка может быть использована как ключ или часть ключа. Для этого необходимо добавить аннотацию @Id в определение свойства. Пример:
@Entity
@IdClass(AdditionalDetailKey.class)publicclass AdditionalDetail {// JoinColumn is also specified in AditionalDetailKey because// a bug in Hibernate, see http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
@Id @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="SERVICE")private Service service;
@Id @Hidden
privateint counter;
...
}
Рассмотрим пример класса-ключа:
publicclass AdditionalDetailKey implements java.io.Serializable{
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="SERVICE")private Service service;
@Hidden
privateint counter;// equals, hashCode, toString, getters and setters
...
}
Вам придется создать ключевой класс, если вы используете составной ключ.
Мы рекомендуем использовать такой подход только, если вы работаете с уже существующей базой данных, структуру которой нельзя изменить. Если вы сами проектируйте схему хранения или способны на нее повлиять, лучше использовать автогенерируемые идентификаторы (autogenerated ids).
Коллекции объектов (Collections)
OpenXava позволяет работать с коллекциями ссылок на сущности. Коллекция объектов в OX - это ссылочное свойства с типом java.util.Collection.
Синтаксис коллекции объектов:
@Size (HV, необязательная анотоация): Минимальное (min) и/ли максимальное (max) ожидаемое количество элементов. Это число проверяется перед сохранением в БД.
@Condition (OX, необязательная аннотация): Является аналогом WHERE в SQL. Задает условие фильтрации объектов, которые должны быть включены в коллекцию.
@OrderBy (JPA, необязательная аннотация): Задает порядок сортировки объектов в коллекции.
@XOrderBy (OX, необязательная аннотация): Конструкция @OrderBy, которая является частью стандарта JPA, не позволяет использовать квалифицированные свойства (т.е. свойства с указанием сущности, к которой они относятся. Другое название - свойства ссылок - properties of references). Конструкция @XOrderBy позволяет это.
Collection declaration (Определение коллекции): Обычное Java-определение переменной-члена класса с типом Collection и getter- и setter- методами. Коллекция должна иметь аннотацию @OneToMany (JPA) или @ManyToMany (JPA) и ссылаться на другую сущность.
В примере выше внутри сущности Invoice мы определили коллекцию объектов deliveries, ассоциированную с Invoice. Более подробная информация о объектно-релационных проекциях вы найдете в главе. В примере мы используем mappedBy="invoice", чтобы указать, что счет доставки (invoice of Delivery) используется для проецирования данной коллекции.
Посмотрим код, который использует такое определение коллекции:
В примере для каждой доставки delivery из ассоциированных с данным счетом invoice выполяентся некий метод doSomething().
Рассмотрим более сложный пример, использующий ту же сущность Invoice:
Используйте CascadeType.REMOVE, если вы хотите, чтобы вместе с головным объектом (invoice в нашем случае) удалялись дочерние объекты (details).
Используйте @OrderBy, если выхотте, чтобы результат был отсортирован по в обратном порядке (desc) по serviceType.
Ограничение @..Size(min=1) приводит к тому, что OX будет требовать, чтобы хотя бы 1 экземпляр объекта коллекции details был создан, чтобы основной объект invoice считался корректным.
С помощью конструкции @Condition вы можете полностью контролировать условия выборки данных для коллекции:
@Condition("${warehouse.zoneNumber} = ${this.warehouse.zoneNumber} AND " +
"${warehouse.number} = ${this.warehouse.number} AND " +
"NOT (${number} = ${this.number})")publicCollection getFellowCarriers(){returnnull;}
Если данная коллекция - часть сущности Carrier, приведённым условием в @Condition мы получим объекты carriers, расположенные на том же складе warehouse, но не. As you see you can use this in the condition in order to reference the value of a property of current object. Конструкция @Condition применима только к пользовательскому интерфейсу, создаваемому OpenXava. Если вы напрямую вызовите getFellowCarriers(), метод вернет null.
Если @Condition недостаточно, чтобы сформировать коллекцию в соответствие с бизнес-требованиями, вы можете самостоятельно перекрыть программный код, возращающий коллекцию. Предыдущий пример может быть реализован следующим образом:
publicCollection getFellowCarriers(){Query query = XPersistence.getManager().createQuery("from Carrier c where " +
"c.warehouse.zoneNumber = :zone AND " +
"c.warehouse.number = :warehouseNumber AND " +
"NOT (c.number = :number) ");
query.setParameter("zone", getWarehouse().getZoneNumber());
query.setParameter("warehouseNumber", getWarehouse().getNumber());
query.setParameter("number", getNumber());return query.getResultList();}
Как вы видете, это обычный getter-метод. Естественно, данный метод должен возвращать коллекцию java.util.Collection, элементами которой должны быть объкты типа Carrier.
Ссылки в коллекциях являются двунаправленными (bidirectional). Это означает, что если в объекте Seller определена коллекция customers, то в соответствующих объектах Customer необходимо иметь ссылку на соответствующий объект Seller.
Кроме того, возможно в объекте Customer иметь больше одной ссылки на объекты Seller (например, seller и alternateSeller). В этом случае JPA не может понять, какое свойство использовать, поэтому появляется аттрибут mappedBy у аннотации @OneToMany. Приведем пример использования:
В данном примере покупатель Customer связан с коллекцией штатов/государств states, но также State может быть связан с несколькими покупателями (customers).
Методы (Methods)
Методы определяются как обычно. Класс OpenXava-сущность (на самом деле, JPA entity) - это самый обычный класс Java. Пример:
Методы - это основа объектнов, т.к. без них объект всего лишь обёртка данных. Мы рекомендуем всегда, когда это возможно, реализовывать бизнес-логику внутри методов (model layer), а не действий (controller layer).
Методы для поиска в БД (Finders)
Метод для поиска в БД - это узкоспециализированный статический метод, предназначенный для поиска одного объекта или коллекции экземпляров объектов, удовлетворяющих определенным критериям.
Несколько примеров:
publicstatic Customer findByNumber(int number)throws NoResultException {Query query = XPersistence.getManager().createQuery("from Customer as o where o.number = :number");
query.setParameter("number", number);return(Customer) query.getSingleResult();}publicstaticCollection findAll(){Query query = XPersistence.getManager().createQuery("from Customer as o");return query.getResultList();}publicstaticCollection findByNameLike(String name){Query query = XPersistence.getManager().createQuery("from Customer as o where o.name like :name order by o.name desc");
query.setParameter("name", name);return query.getResultList();}
Теперь несколько примеров использования методов для поиска:
Обращаем ваше внимание, что использование методов для поиска позволяет улучшить читаемость создаваемого кода в сравнении с многословными запросами к API стандарта JPA. Но это всего лишь рекомендация к стилю программирования, никто не запрещает вам напрямую использовать запросы к JPA.
Проверка для перечислимого типа (Entity validator)
Аннотация @EntityValidator предназначена для реализации проверок на уровне сущности (model level). Данная аннотация должна использоваться для общих проверок - когда выполняется валидация несколько свойств одновременно и данные проверки не могут быть логически соотносены к конкретным свойствам.
Синтаксис:
value (обязательный аттрибут): Класс, который реализует логику проверки (класс-валидатор). Данный класс должен реализовывать интерфейс IValidator.
onlyOnCreate (необязательный аттрибут): Если данный аттррибут равен true, то проверка будет выполняться только в момент создания нового объекта. но не будет выполнятся в момент изменения уже существующего объекта. Значение по-умолчанию false.
properties (несколько @PropertyValue, необязательный аттрибут): Предоставляет возможность установить значения свойств-параметров вызова класса-валидатора.
Класс-валидатор должен реализовывать интерфейс IValidator (1), что принуждает к реализации метода validate(Messages messages) (2). В данном методе выполняются проверки, если проверки не проходят (выполняются с ошибкой), то вызывается errors.add(..) (3), используя идентификаторы сообщений из ресурсного фалйаi18n и параметров для подстановки в сообщение. Если процесс валидации выполняется с ошибкой (данный процесс сопровождается вызовом всех зарегистрированных валидаторов), то OpenXava не сохраняет объект и выводит сообщения об ошибках пользователю (созданные внутри метода validate()).
В нашем примере выполняется общая проверка для свойств description и unitPrice. В таких случаях рекомендуется выполнять проверку на уровне сущности (model level).
Для одной сущности можно реализовать несколько @EntityValidators. Пример:
Аннотация @RemoveValidator также реализован на уровне сущности (model level) и предназначен для выполнения проверки перед удалением объекта. Данная проверка позволяет запретить удаление объекта.
Синтаксис:
packageorg.openxava.test.validators;importorg.openxava.test.model.*;importorg.openxava.util.*;importorg.openxava.validators.*;/**
* @author Javier Paniza
*/publicclass DeliveryTypeRemoveValidator implements IRemoveValidator {// 1private DeliveryType deliveryType;privateint number;// We use this (instaed of obtaining it from deliveryType)// for testing @PropertyValue for simple propertiespublicvoid setEntity(Object entity)throwsException{// 2this.deliveryType = (DeliveryType) entity;}publicvoid validate(Messages errors)throwsException{if(!deliveryType.getDeliveries().isEmpty()){
errors.add("not_remove_delivery_type_if_in_deliveries", newInteger(getNumber()));// 3}}publicint getNumber(){return number;}publicvoid setNumber(int number){this.number = number;}}
Класс-валидатор должен реализовывать интерфейс IRemoveValidator (1) , что принуждает реализовывать метод setEntity() (2), который получает объект для удаления. Если при выполнении метода validate() (3) в объект Messages будет добавлено одно или более сообщение об ошибке, то считается, что проверка выполнилась с ошибкой. Если после выполнения всех проверок есть хотя бы одна ошибка валидации, то OpenXava не удаляет объект и выводит сообщения с ошибками валидации пользователю.
В нашем примере валидатор проверяет, есть ли доставки (deliveries), которые используют данный тип доставки (delivery type) прежде, чем удалить данный тип доставки.
Также как в случае с @EntityValidator, можно использовать несколько @RemoveValidator для сущности, обернув их аннтоацией @RemoveValidators. @RemoveValidator выполняется в момент, когда пользователь пытается удалить сущность средствами OpenXava (использование MapFacade или стандартных действий (actions) OX), но не будет выполняться, если вы попытаетесь напрямую использоваться вызовы JPA. При прямом использовании JPA для создания констрейна на удаление используйте аннотацию @PreRemove JPA.
Методы обратного вызова JPA (JPA callback methods)
@PrePersist - контролируемая вставка данных в БД
Используя аннотацию @PrePersist, вы можете встроить свою собственную логику в процесс выполнения сохранения объекта в БД.
Пример:
В нашем примере, каждый раз при создании сущности DeliveryType в свойство description добавляется суффикс.
@PreUpdate - контролируемое изменение данных в БД
Используя аннотацию @PreUpdate, вы можете встроить свою собственную логику, которая будет выполняться после перехода сущности в определенное состояние или перед тем, как сохранять изменения сущности в БД, т.е., просто перед выполнением выражения UPDATE в БД.
Пример:
В этом примере при люьбом изменении DeliveryType в описание добавляется суффикс.
Как вы наверное уже убедились, логика использования @PreUpdate аналогична @PrePersist, разница только в моменте, когда соответствующий класс вызывается.
Как сказано в спецификации JPA: "Сущность может использовать другие хорошо спроектированные классы для отображения состояния сущности. Экземпляры данных классов, в отличие от собственно экземпляров сущности, не должны самостоятельно отображать сохраняемую сущность (do not have persistent identity). В противоположность, они существуют только как встраиваемые объекты той сущности, к которой они относятся. Такие встраиваемые объекты должны строго принадлежат сущности-владельцу и не должны использоваться несколькими сохраняемыми сущностями (are not sharable across persistent entities)."
Оригинальный текст:
("An entity may use other fine-grained classes to represent entity state. Instances of these classes, unlike entity instances themselves, do not have persistent identity. Instead, they exist only as embedded objects of the entity to which they belong. Such embedded objects belong strictly to their owning entity, and are not sharable across persistent entities.")
Синтраксис встраиваемого класса:
@Embeddable (JPA, одно вхождение, обязательная аннотация): Указывает на то, что класс является встраиваемым классом JPA. Иными словами, экземпляры данного класса будут частями сохраняемых объектов.
Class declaration: Обычное определение класса Java. Можно использовать наследование (оператор extends) и реализовывать интерфейсов (оператор implements).
Properties: Обычные свойства (переменные-члены класса и getter- /setter- методы) Java.
References: Ссылки на другие сущности. Данная конструкция не поддерживается стандратом JPA 1.0 (EJB 3.0), но реализация Hibernate поддерживает это.
Methods: Методы, реализующие бизнес-логику.
Встроенная ссылка (Embedded reference)
Возможно использование встраиваемых свойств-ссылок.
В рассматриваемом далее примере определяется класс Address (аннотированный с помощью @Embedded), на который ссылается основной класс-сущность. В основной сущности встроенная ссылка определяется следующим образом:
@Embedded
private Address address;
Сам класс Address необходимо определить как встраиваемый:
packageorg.openxava.test.model;importjavax.persistence.*;importorg.openxava.annotations.*;/**
*
* @author Javier Paniza
*/
@Embeddable
publicclass Address implements IWithCity {// 1
@Required @Column(length=30)privateString street;
@Required @Column(length=5)privateint zipCode;
@Required @Column(length=20)privateString city;// ManyToOne inside an Embeddable is not supported by JPA 1.0 (see at 9.1.34),// but Hibernate implementation supports it.
@ManyToOne(fetch=FetchType.LAZY, optional=false) @JoinColumn(name="STATE")privateState state;// 2publicString getCity(){return city;}publicvoid setCity(String city){this.city = city;}publicString getStreet(){return street;}publicvoid setStreet(String street){this.street = street;}publicint getZipCode(){return zipCode;}publicvoid setZipCode(int zipCode){this.zipCode = zipCode;}publicState getState(){return state;}publicvoid setState(State state){this.state = state;}}
Как вы видете, встраиваемый класс может реализовывать интерфейс (1), содержать свойства-ссылки (2) и т.д., но не может использовать сохранямые коллекции или вызывть методы обратного вызова JPA.
Использовать объект Address можно следующим образом (если нам необходимо прочитать его):
Customer customer = ...
Address address = customer.getAddress();
address.getStreet();// to obtain the value
Другой вариант работы с объектом Address:
// to set a new address
Address address = new Address();
address.setStreet(“My street”);
address.setZipCode(46001);
address.setCity(“Valencia”);
address.setState(state);
customer.setAddress(address);
В нашем примере мы использовали простую ссылку на Address (не коллекцию), и генерироваться будет код простого JavaBean, чей жизненный цикл будет ассоцирован с объектом-контейнером. Т.е. Address добавляется и удаляется только через контейнер Customer. Еще раз обращаем ваше внимание на то, что встраиваемый класс Address не имеет своего жизненного цикла и не может разделяться между несколькими (использоваться другими) Customer.
Встраиваемые коллекции (Embedded collections)
Встраиваемые коллекции не поддерживаются JPA 1.0. Тем не менее, разработчик сам может реализовать их, используя коллекции сущностей и каскадные типы (cascade type) REMOVE или ALL. OpenXava обрабатывает такие коллекции специальным способом так, как если бы они были встраиваемыми коллекциями.
Рассмотрим выше сказанное на примере. В сущности-контейнере (в нашем примере, это Invoice) необходимо определить следующую коллекцию:
В примере был представлен класс, описывающий сложную сущность, с калькуляторами, валидаторами, ссылками и прочие. Кроме этого вы можете здесь определить ссылку на класс-контейнер (invoice). Если вы это сделаете это, то при удалении Invoice будут удаляться все InvoiceDetail. Также наличие такой ссылки влияет на генерируемый пользовательский интерфейс (более подробную информацию вы найдете в главе Представление).
Наследование (Inheritance)
OpenXava поддерживает наследование Java и наследование JPA.
Рассмотрим пример. Мы можем использовать @MappedSuperclass следующим образом:
packageorg.openxava.test.model;importjavax.persistence.*;importorg.hibernate.annotations.*;importorg.openxava.annotations.*;/**
* Base class for defining entities with a UUID oid. <p>
*
* @author Javier Paniza
*/
@MappedSuperclass
publicclass Identifiable {
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy = "uuid")privateString oid;publicString getOid(){return oid;}publicvoid setOid(String oid){this.oid = oid;}}
Можно использовать несколько уровней наследования. Т.к. можно определить аттрибут @MappedSuperclass для класса, который уже наследует другой класс:
packageorg.openxava.test.model;importjavax.persistence.*;importorg.openxava.annotations.*;/**
* Base class for entities with a 'name' property. <p>
*
* @author Javier Paniza
*/
@MappedSuperclass
publicclass Nameable extends Identifiable {
@Column(length=50) @Required
privateString name;publicString getName(){return name;}publicvoid setName(String name){this.name = name;}}
Теперь мы можем использовать оба класса (и Identifiable, и Nameable), чтобы определять экземпляры:
packageorg.openxava.test.model;importjavax.persistence.*;/**
*
* @author Javier Paniza
*/
@Entity
@DiscriminatorColumn(name="TYPE")
@DiscriminatorValue("HUM")
@Table(name="PERSON")
@AttributeOverrides(
@AttributeOverride(name="name", column=@Column(name="PNAME")))publicclass Human extends Nameable {
@Enumerated(EnumType.STRING)private Sex sex;publicenum Sex { MALE, FEMALE };public Sex getSex(){return sex;}publicvoid setSex(Sex sex){this.sex = sex;}}
Наконец определим рельную сущность на базе выше перечисленных классов:
Совет: Вы можете создать модуль OpenXava для Human (Человке) и Programmer (Программист), а не для классов Identifiable или Nameble напрямую). В модуле Programmer module пользователь получить доступ только к "программистам". А в модуле Human пользователь сможет работать со всеми объектами Human и Programmer. Более того, когда пользователь попытается просмотреть карточку "программиста" из модуля Human module, именно представление Programmer будет отображено. Это то, что я называю, настоящим полиформизмом.
Остановимся на проецировании - аннотация @AttributeOverrides поддерживается, но, на настоящий момент, только стратегия иерархии "table" = "class" работает корректно.
Составной ключ (Composite key)
Предпочтительный способ определения ключа в таблице (в сущности) - это простой автогенерируемый ключ (почитайте про аннотации @Id и @GeneratedValue), но иногда, чаще всего, когда вы работаете с уже существующей базой данных, на структуру которой вы не можете повлиять, вам небходимо проецировать сущность в таблицу, в которой ключ состоит из нескольких стобцов. Данный случай может быть реализован в JPA (и, соответственно, в OpenXava) двумя путями: используя одну из аннтоаций @IdClass или @EmbeddedId
Класс Id (Id class)
В этом подходе аннотация @IdClass внутри вашей сущности указывает на ключевой класс. В этом случае свойства-части ключа описываются прямо в вашей сущности, при этом вам необходимо промаркировать их аннотацией @Id:
packageorg.openxava.test.model;importjavax.persistence.*;importorg.openxava.annotations.*;importorg.openxava.jpa.*;/**
*
* @author Javier Paniza
*/
@Entity
@IdClass(WarehouseKey.class)publicclass Warehouse {
@Id
// Column is also specified in WarehouseKey because a bug in Hibernate, see// http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
@Column(length=3, name="ZONE")privateint zoneNumber;
@Id @Column(length=3)privateint number;
@Column(length=40) @Required
privateString name;publicString getName(){return name;}publicvoid setName(String name){this.name = name;}publicint getNumber(){return number;}publicvoid setNumber(int number){this.number = number;}publicint getZoneNumber(){return zoneNumber;}publicvoid setZoneNumber(int zoneNumber){this.zoneNumber = zoneNumber;}}
Затем вам необходимо определить ваш id-класс, который представляет собой обычный сериализирумый (serializable) класс, который содержит все ключевые свойства вашей сущности-контейнера:
Чтобы использовать встраеваемый Id, необходимо использовать аннотацию @EmbeddedId. Определение же такого класса делается аннотацией @Embeddable . В отличие от использования @IdClass вам не нужно описывать свойства-части ключа в основной сущности.
Стандартный метод проверки Hibernate (Hibernate Validator) (начиная с v3.0.1)
OpenXava полность поддерживает возможности Hibernate Validator. Вы можете создавать свои собственные ограничения (constrains) для ваших сушностей (обратитесь к документации Hibernate Validator documention за более подробной информацией) и OpenXava расспознает их и будет выводить соответствующие сообщения, если валидация не пройдет, пользователю.
Более того, аннотации OpenXava @Required, @PropertyValidator и @EntityValidator сами по себе определены как Hibernate Validator constraints, что означает, что если вы будет использовать прямоц доступ к JPA, данные валидации будут корректно работать.
С другой стороны аннотации @RemoveValidator, @PropertyValidator(onlyOnCreate=true), EntityValidator(onlyOnCreate=true) и Метод проверки исходного значения реализованы только в OpenXava и не распознаются Hibernate Validator или JPA.
Table of Contents
Глава 3: Модель (Model)
Слой (layer), называемый Моделью (Model), в объектно-ориентированном приложении содержит бизнес-логику. Которая обычно состоит из структур данных, вычислительной или прикладной логики, проверок и прочего, ассоциированных с данными.OpenXava - это ориентированный на модель фреймворк, в котором модель имеет наибольшее значение, и все остальное (например, пользовательский интерфейс) зависит от неё или является её производным.
В последних версиях OpenXava для специфицирования модели используются Plain Old Java Objects (POJO) - простые Java классы + используются аннотации, которые стали доступны в Java5. Также возможно определение модели с помощью XML, которое было реализованно в более раних версиях OX.
Из описанной вами модели OpenXava способен создавать полностью функциональные веб приложения.
Бизнес-компонент (Business Component)
Фундаментом OpenXava является понятие бизнес-компонента (business component). Бизнес-компонент опреляется с помощью Java объекта, именнуемого в OX сущностью (Entity). Данный класс является обычным EJB3 компонентом, или другими словами, классом POJO с аннотациями, реализованными в соответствии со стандартом Java Persistence API (JPA).JPA - это стандарт Java для постоянного хранения объектов (persistence). В первую очередь он предназначен для сохранения и извлечения состояния объектов Java в/из базы данных. Если вы имете опыт разработки JPA с использованием POJO-объектов, считайте, что вы уже умеете разрабатывать приложения OpenXava.
Давайте поймем, что мы можем реализовать, определив Бизнес-компонент, который на самом деле является простым Java классом:
- Model (Модель): Структуры данных, проверки, вспомогательные вычисление и многое другое.
- View(Представление): Это, как модель будет отображаться пользователю.
- Tabular data(Табличное представление или списки объектов): Специальное представление для презентации списка объектов в режиме списка (по-другому, таблицы).
- Object/relational mapping(Правила сохранения объектов в базу данных): Описывает, как будет сохраняться и извлекаться состояние объектов в/из базы данных.
Данная глава посвещена определению модели данных.Сущность (Entity)
Для того, чтобы определить модель, нужно начать с создания обычного класса Java. Дополнительная информация, необходимая для OX, определяется с помощью аннотаций. OpenXava поддерживает аннотации JPA и Hibernate Validator. Кроме этого, OpenXava определяет целый ряд собственных аннотаций для управления визуализацией бизнес-компонентов и другой функциональностью.Аббревиатура JPA в тексте данной главы указывает аннонтации Java Persistent API, аббревиатурой HV обозначены аннотации Hibernate Validator, а аббревиатура OX указывает аннотации OpenXava.
Синтаксическое определение entity следующее:
Свойства сущности (Properties)
Свойство представляет собой один отдельный аттрибут сущности. В OX свойство реализуется как переменная-член класса Java. От обычной переменной члена класса свойство отличается тем, что свойство обычно имеет область видимости private, а также реализованы методы get/set (которые называются геттеры и сеттеры на программистском сленге) для чтения и установки значения свойства. Например, свойство X будет иметь методы setX и getX. Иногда свойство имеет только один из методов, например, если это вычислимое свойство, но сейчас мы не будем рассматривать такой случай.Синтаксическое определение свойства в OX следующее:
Стереотип (Stereotype)
Стереотип (@Stereotype) позволяет упростить определение и сопровождение сложного поведения свойств. В большом приложении обычно во многих сущностях используются похожие или идентичные свойства. Стереотип позволяет избежать необходимость описывать / реализовывать все аспекты поведения и логики для каждого отдельного свойства. Все похожие свойства могут быть реализованы на базе одного стереотипа. Разработчик может создать свой или задействовать уже реализованный стереотип. OX уже имеет готовые стереотипы для некоторых распространённых случаев, например: адрес e-mail, название (name), комментарий (comment), описание (description) и т.д. OX реализует поведение по-умолчанию для большинства стандартных типов Java, которые поддерживаются JPA. Но если вы хотите построить действительно профессиональный пользовательский интерфейс - например, настроить проверку корректности значений (validators), размеры по-умолчанию (default sizes), редактор (visual editors) и многое другое специальным образом для конкретных свойств, то сделать это пожет использование стереотипов. Ничто не мешает вам создать собственные стереотипы, например, NAME, MEMO или DESCRIPTION и использовать их для ваших свойств.Ниже приведен список стереотипов, входящих в стандартную поставку OpenXava:
Давайте попробуем создать свой собственный стереотип. Предположим, что вы хотите создать новый стереотип PERSON_NAME, который предназначен для представления Ф.И.О. человека.
Откройте на редактирование (если не существует, то создайте) файл editors.xml в папке xava ваше проекта. Добавьте в файл:
Этим вы определили редактор, который будет использоваться для отображения и редактирования свойств, относящихся к стереотипу PERSON_NAME.
Также вы можете определить размер для свойств, использующих данный стереотип. Делается это в файле default-size.xml вашего проекта:
<for-stereotype name="PERSON_NAME" size="40"/>В этом примере для свойств со стереотипом PERSON_NAME будет использоваться размер 40, если иного не определено в аттрибутах самого свойства.Иногда требуется изменить правила валидации (validator) для стереотипа. Для этого необходимо в файл validators.xml вашего проекта добавить следующее:
В этом примере для стереотипа PERSON_NAME в качестве валидатора указан класс NotBlankCharacterValidator из пакета org.xava.validators.
Теперь все готово, чтобы определить свойство с использованием стереотипа PERSON_NAME:
В нашем случае значение 40 задает размер, String - это тип и проверка NotBlankCharacterValidator выполняется, если данное свойство является обязательным.
Стереотип "Галлерея изображений" (IMAGES_GALLERY stereotype)
OX имеет встроенную поддержку галерей изображений. Для создания галереи изображений вам необходимо создать свойство сущности, используя стереотип IMAGES_GALLERY stereotype. Пример ниже:Столбец photos в базе данных должен иметь тип String и размер 32 символа (VARCHAR(32)).
Мы закончили определение свойства, использующего стереотип IMAGES_GALLERY.
Для того, чтобы создать работающее приложение, нам понадобиться выполнить еще несколько простых шагов.
Первое, что нужно сделать, - это создать таблицу в базе данных для хранения изображений:
Тип BLOB поддерживают не все базы данных. Поэтому в качестве типа данных для столбца IMAGE вам необходимо выбрать тип данных, наиболее подходящий для хранения массива байтов (byte []).
Далее нужно внести изменения в persistence/hibernate.cfg.xml вашего проекта:
Теперь вы можете использовать стереотип IMAGES_GALLERY в вашем приложении.
Параллельное исполнение запросов и свойство Version (Concurrency and version property)
Под параллельным исполнением запросов подразумевается возможность приложения обеспечивать нескольким пользователям одновременно изменять данные. OpenXava использует оптимистическую модель управления транзакциями JPA. При использовании такой модели записи в базе данных не блокируются, что позволяет получить парарлельную работу пользователей без потери целостности данных.Например, пусть пользователь A прочитал запись. После этого пользователь B прочитал ту же запись, модифицировал ее и сохранил изменения в БД. В том случае, если пользователь A попытается сохранить свои изменения, он получит сообщение об ошибке и будет вынужден заново загрузить данные и повторить свои изменения.
Чтобы задействовать оптимистическую модель управления транзакциями, вам необходимо определить свойство с аттрибутом @Version в вашей сущности. Пример определения такого свойства:
Данное свойство предназначено для использования JPA, поэтому ваше приложение или пользователи не должны использовать данное свойство явным способом.
Перечислимые типы (Enums)
OpenXava поддерживает введённые в Java 5 перечислимые типы enums. Используя enum, вы можете определить свойство, которое может принимать только значения из фиксированного набора, заданного при объявлении enum.Пример:
В данном примере свойство distance может принимать только следующие значения: LOCAL, NATIONAL or INTERNATIONAL, и если вы не указали аттрибут @Required, то значение null также допустимо.
В текущей реализации пользовательского интерфейса такое свойство представляется в виде combo. Текст метки для каждого значения берётся из файлов i18n.
В базе данных по умолчанию значение сохраняется как integer (в нашем примере, 0 - это LOCAL, 1 - NATIONAL, 2 - INTERNATIONAL и null, если значение не определено). Тем не менее, вы можете использовать другой тип, что удобно, если вы работаете с уже существующей базой данных. Более подробная информация дана в другой главе.
Вычисляемые свойства (Calculated properties)
Вычислимые свойства не могут быть изменены явным способом (т.к. имеют только метод установки значения getter) и не сохраняются в базе данных (соответственно, не подлежат проецированию на столбцы в БД).Ниже приведен пример определения вычислимого свойства:
Использовать выше представленное определение можно так:
Result будет равен 332.772.
Свойство unitPriceInPesetas будет отображаться пользователю нередактируемым. Визуальная длина данного поля будет 10, что определяется путем задания аттрибута @Max(9999999999L) (2). Т.к. мы указали аттрибут @Depends("unitPrice") (1), то если пользователь изменит значение свойства unitPrice в пользовательском интерфейсе, то значение свойства unitPriceInPesetas будет пересчитано и новое значение будет отображено пользователю.
В getter-методе вычислимого свойства вы можете возможности JDBC, как показано в этом примере:
Прямое использование JDBC выглядит не очень модно, но позволяет улучшить производительность приложения. Класс DataSourceConnectionProvider дает возможность получить соединение JDBC, ассоциированное с источником данных для указанной сущности (в нашем случае это Invoice). Данный класс был добавлен в OX для удобства разработки, но вы можете подключаться к JDBC с использованием JNDI или любым иным удобным вам способом. На самом деле вы можете использовать любой код Java для расчета значения вычислимого свойства.
Если вы используете доступ на базе свойств (property-based access),то вам необходимо добавить аннотацию @Transient для вычислимого свойства. Пример:
Вычислитель исходного значения (Default value calculator)
Используя аттрибут @DefaultValueCalculator, вы можете задать для свойства класс, который будет определять исходное значение для данного свойства. Такой класс в OX называется калькулятором или вычислителем исходного значения. Например:В этом случае, когда пользователь попытается создать новый экземпляр сущности, он увидит, что значение для свойства "year" уже задано. Далее, если это необходимо, пользователь может изменить данное значение. Логика для вычисления исходного значения определена в классе CurrentYearCalculator:
Существуют и другие варианты использования @DefaultValueCalculator:
В данном случае для вычисления исходного значения OpenXava создает экземпляр StringCalculator, устанавливает значение "GOOD" для свойства string экземпляра StringCalculator, и в конце вызывает его метод calculate() для вычисления значения свойства relationWithSeller. Как вы увидели, используя аннотацию @PropertyValue, вы можете создавать калькуляторы, подходящие для многократного использования.
Анотация @PropertyValue также позволяет использовать значения свойств других сущностей, например:
В данном случае OpenXava заполняет свойство drivingLicenceType посредством использования класса CarrierRemarksCalculator значением отображаемого свойства type из ссылочной сущности drivingLicence. Обратите внимание, что аттрибут from поддерживает ссылки на свойства (reference.property).
Также можно использовать @PropertyValue без from или value:
В данном случае OpenXava использует значение отображаемого свойства familyNumber и вставляет значение в свойство familyNumber калькулятора. Выражение @PropertyValue(name="familiyNumber") эквивалентно @PropertyValue(name="familiyNumber", from="familyNumber").
Внутри класса-калькулятора вы можете использовать возможности JDBC напрямую. Вот пример:
Пример калькулятора:
Чтобы использовать прямые вызове JDBC в калькуляторе, класс должен реализовывать интерфейс IJDBCCalculator (1), включая определение переменную-член класса IConnectionProvider (2).
Уже реализованные калькуляторы вы найдете в пакете org.openxava.calculators из поставки OpenXava.
Исходные значения при создании (Default values on create)
Вы можете определить значение свойства, которое будет вычисленно и использоваться только при создании экземпляра сущности (при вставке записи в БД).Значения свойств первичного ключа обычно вычисляются с применением JPA.
Например, автоикрементное значения для первичного ключа реализуется так:
Можно реализовать варианты заполнения ключевых столбцов, например, использование SEQUENCE объектов БД(также реализация по стандарту JPA):
Для реализации уникального идентификатора UUID (тип String размером 32 символов), можно использовать расширение Hibernate стандарта JPA:
Более подробную информацию об аннотации @GeneratedValues вы найдете в главе 9.1.9 спецификации стандарта JPA 1.0 (часть JSR-220).
Если вам необходимо реализовать нестандартную логику для вычисления значения ключевого свойства при создании объекта или вам нужно генерировать значения неключевого свойства, то вместо @GeneratedValue, вы вам придется использовать следующую конструкцию:
Анотация @PrePersist (JPA) определяет метод, который будет выполнен перед первой вставкой данных в БД. В данном методе вы можете реализовать любую самую экзотическую логику для вычисления значения любых свойств: и ключевых, и неключевых.
Проверка значения свойства (Property validator)
Анонтация @PropertyValidator позволяет указать класс (назовём его валидатором), который будет выполнять логику проверки значения свойства перед тем, как данное значение присвоить свойству. Свойство может одновременно иметь несколько проверок и, соответственно, конструкций @PropertyValidator:Техника конфигурирования валидатора примерно такая же как для калькулятора. За исключением того, что в аннотации @PropertyValue нет аттрибута from и вам нужно всегда использовать аттрибут value.
Указав аттрибут onlyOnCreate=”true”, вы создадите валидацию, которая будет выполняться только при создании объекта и никогда при модификации.
Пример класса-валидатора:
Давайте рассмотри валидатор подробнее. Валидатор должен реализовывать интерфейс IPropertyValidator (1), что вынуждает класс иметь метод validate(), где, собственно, и происходит проверка значения.
Параметры метода validate() следующие:
- Messages errors: Объек типа Messages, который представляет собой набор сообщений об ошибках, возникших при валидации. При создании своего валидатора вам необходимо добавлять сюда сообщения обо всех ошибках валидации, о которых вы хотите сообщить пользователю.
- Object value: Значение, которое мы будет проверять.
- String objectName: Имя объекта, к которому относится свойство, которое мы проверяем. Полезно для вывода в сообщения об ошибках.
- String propertyName: Имя свойства для валидации. Полезно для вывода в сообщения об ошибках.
Итак, если вы нашли ошибку (значение не прошло одну из проверок), вам необходимо сообщить об этом, добавив сообщение вызовом метода errors.add()). Для вызова данного метода необходимо задать идентификатор и агрументы сообщения. Идентификатор сообщения указывает на сообщение, определенное в ресурсном файле i18n вашего проекта. Вот пример определения сообщения:exclude_string={0} cannot contain {2} in {1}Параметры {0}..{2} подменяются на аргументы, которые вы передаете в вызове errors.add().Если идентфикатор сообщения не найдет в ресурсном файле, то вместо текста сообщения будет отображен его идентификатор.
Проверка значения (валидация) считается успешной, если ни одного сообщения не было добавлено в Messages errors. OpenXava собирает в единый список все сообщения от всех валидаторов перед сохранением объекта, и если список сообщений не пуст, то отображает данные сообщения и не позволяет пользователю сохранить объект.
В пакете org.openxava.validators вы найдете уже реализованные валидаторы.
Напомним, что @PropertyValidator определен как Hibernate Validator contraint (начиная с версии v3.0.1).
Проверка значения свойства для стереотипа или типа (Default validator) (начиная с версии v2.0.3)
Вы можете определить валидатор значения свойства на уровне типа или стереотипа свойства. Для этого необходимо в файле xava/validators.xml вашего проекта определить соответствующий валидатор.К примеру, добавьте в файл xava/validators.xml вашего проекта следующее:
В этом примере мы привязываем валидатор PersonNameValidator к стереотипу PERSON_NAME. Теперь, если вы определите свойство на данном стереотипе:
Значение данного свойства будет проверяться валидатором PersonNameValidator, несмотря на то, что само свойство не определяет никаких валидаторов. Валидатор PersonNameValidator будет применяться для всех свойств со стереотипом PERSON_NAME.
Примерно также вы можете создать валидатор для типа.
В файле validators.xml также можно определять валидаторы, выполняющиеся при проверке, представлено ли значение для обязательного свойства (аннотация @Required).
Также можно назначать имена (alias - алиасы) для классов-валидаторов.
Больше информации о валидаторах вы можете почерпнуть в файлах OpenXava/xava/validators.xml и OpenXavaTest/xava/validators.xml.
Обращаем ваше внимание, что валидаторы не сработают, если вы используете напрямую JPA api для сохранения ваших объектов в БД.
Ссылки (References)
Ссылки позволяют описать связь между сущностями. Ссылка реализуется как свойство (имеющее getter- и setter-методы), при этом тип данного свойства должен соответствовать одной из сущностей-моделей OX. Например, Customer может ссылаться на Seller, что позволит вам написать примерно такой код:Метод getName() в примере возвращает имя покупателя.
Синтаксическое определение ссылки таково:
- @ManyToOne(optional=false) (JPA), @Required (OX) (необязательная аннотация, рекомендуем использовать вариант JPA): Указывает, что ссылка является обязательной. В момент сохранения OpenXava проверяет, все ли обязательные свойства-ссылки заполнены. Если данное требование не выполнено, то сохранение прерывается и пользователю выводится список сообщений об ошибках.
- @Id (JPA, небязательная аннотация): Указывает, что свойсво-ссылка является частью ключа. Комбинация ключевых свойств и ссылочных свойств должна быть спроецирована на группу столбцов базы данных с уникальной кобинацией значений, обычно, это первичный ключ.
- @SearchKey (OX, необязательная аннотация): (Начиная с версии v3.0.2) Ключ поиска используется для поиска объектов пользователем в пользовательском интерфейсе. Редактируемые поля, ассоцириуемые с каждым свойством, доступным для поиска, в пользовательском интерфейсе позволяют вводить значения для поиска (фильтрации). По-умолчанию для реализации поиска OpenXava использует свойства, анотированные. Если же данные свойства скрыты, используется первое свойство в представлении. Анотация @SearchKey позволяет точно настоить поиск в пользовательском интерфейсе.
- @DefaultValueCalculator (OX, допускается только одно вхождение аннотации, необязательная аннотация): Реализует логику вычисления исходного (начального) значения свойства-ссылки. Класс-калькулятор, реализующий вычисление исходного значения, должен возвращать действительное значение, соответствующее реально существующему первичном ключу сущности, на которую реализуется ссылка. При этом, значение может быть простого типа, а может быть сложным объектом-ключом (если ключ реализуется как отдельный объект).
- Reference declaration (Определение свойства-ссылки): Обычное определение свойства на языке Java (закрытая (private) переменнная-член класса и getter- и setter-методы). Свойство-ссылка всегда имеет аннотацию @ManyToOne (JPA) и тип свойства всегда соответствует другой сущности.
Примеры ссылок:- Свойство-ссылка seller ссылается на сущность Seller.
- Свойство-ссылка alternateSeller также реализует ссылку на сущность Seller. В данном случае вы указываем режим выборки fetch=FetchType.LAZY, что означает, что данные из базы данных будут загружаться только по мере необходимости (когда к ним происходит обращение). Это более эффективный подход с точки зрения производительности, чем поведение JPA по-умолчанию. Мы советуем всегда использовать fetch=FetchType.LAZY при определении свойств-ссылок.
Если предположить, что выше приведённое определение ссылок-свойств было выполнено для сущности Customer, то можно написать следюущий код, использующий данные свойства:Вычислитель исходного значения для ссылки (Default value calculator in references)
Для свойства-ссылки аннотация @DefaultValueCalculator работает совершенно также как для обычного свойства с той только разницей, что должно возвращаться значение, соответствующее реальной ссылке.Для простого ключа определение калькулятора исходного значения может быть следующим:
Метод вычисления calculate() может выглядеть так:
Из примера видно, что возвращается значение integer. Для свойства family будет возвращаться значение 2, определенное как @PropertyValue(name="value", value="2").
Определение сложного ключа (composed key) для сущности:
А теперь рассмотрим код калькулятора, который работает с композитным ключом:
Класс возращает объект типа WarehouseKey.
Использование ссылок как ключей (Using references as key)
Свойство-ссылка может быть использована как ключ или часть ключа. Для этого необходимо добавить аннотацию @Id в определение свойства. Пример:Рассмотрим пример класса-ключа:
Вам придется создать ключевой класс, если вы используете составной ключ.
Мы рекомендуем использовать такой подход только, если вы работаете с уже существующей базой данных, структуру которой нельзя изменить. Если вы сами проектируйте схему хранения или способны на нее повлиять, лучше использовать автогенерируемые идентификаторы (autogenerated ids).
Коллекции объектов (Collections)
OpenXava позволяет работать с коллекциями ссылок на сущности. Коллекция объектов в OX - это ссылочное свойства с типом java.util.Collection.Синтаксис коллекции объектов:
- @Size (HV, необязательная анотоация): Минимальное (min) и/ли максимальное (max) ожидаемое количество элементов. Это число проверяется перед сохранением в БД.
- @Condition (OX, необязательная аннотация): Является аналогом WHERE в SQL. Задает условие фильтрации объектов, которые должны быть включены в коллекцию.
- @OrderBy (JPA, необязательная аннотация): Задает порядок сортировки объектов в коллекции.
- @XOrderBy (OX, необязательная аннотация): Конструкция @OrderBy, которая является частью стандарта JPA, не позволяет использовать квалифицированные свойства (т.е. свойства с указанием сущности, к которой они относятся. Другое название - свойства ссылок - properties of references). Конструкция @XOrderBy позволяет это.
- Collection declaration (Определение коллекции): Обычное Java-определение переменной-члена класса с типом Collection и getter- и setter- методами. Коллекция должна иметь аннотацию @OneToMany (JPA) или @ManyToMany (JPA) и ссылаться на другую сущность.
Давайте рассмотрим несколько примеров:В примере выше внутри сущности Invoice мы определили коллекцию объектов deliveries, ассоциированную с Invoice. Более подробная информация о объектно-релационных проекциях вы найдете в главе. В примере мы используем mappedBy="invoice", чтобы указать, что счет доставки (invoice of Delivery) используется для проецирования данной коллекции.
Посмотрим код, который использует такое определение коллекции:
В примере для каждой доставки delivery из ассоциированных с данным счетом invoice выполяентся некий метод doSomething().
Рассмотрим более сложный пример, использующий ту же сущность Invoice:
- Используйте CascadeType.REMOVE, если вы хотите, чтобы вместе с головным объектом (invoice в нашем случае) удалялись дочерние объекты (details).
- Используйте @OrderBy, если выхотте, чтобы результат был отсортирован по в обратном порядке (desc) по serviceType.
- Ограничение @..Size(min=1) приводит к тому, что OX будет требовать, чтобы хотя бы 1 экземпляр объекта коллекции details был создан, чтобы основной объект invoice считался корректным.
С помощью конструкции @Condition вы можете полностью контролировать условия выборки данных для коллекции:Если данная коллекция - часть сущности Carrier, приведённым условием в @Condition мы получим объекты carriers, расположенные на том же складе warehouse, но не. As you see you can use this in the condition in order to reference the value of a property of current object. Конструкция @Condition применима только к пользовательскому интерфейсу, создаваемому OpenXava. Если вы напрямую вызовите getFellowCarriers(), метод вернет null.
Если @Condition недостаточно, чтобы сформировать коллекцию в соответствие с бизнес-требованиями, вы можете самостоятельно перекрыть программный код, возращающий коллекцию. Предыдущий пример может быть реализован следующим образом:
Как вы видете, это обычный getter-метод. Естественно, данный метод должен возвращать коллекцию java.util.Collection, элементами которой должны быть объкты типа Carrier.
Ссылки в коллекциях являются двунаправленными (bidirectional). Это означает, что если в объекте Seller определена коллекция customers, то в соответствующих объектах Customer необходимо иметь ссылку на соответствующий объект Seller.
Кроме того, возможно в объекте Customer иметь больше одной ссылки на объекты Seller (например, seller и alternateSeller). В этом случае JPA не может понять, какое свойство использовать, поэтому появляется аттрибут mappedBy у аннотации @OneToMany. Приведем пример использования:
Такой вариант записи позволяет точно указать, что для данной коллекции используется свойство seller, а не alternateSeller.
Аннотация @ManyToMany (JPA) (Многие ко Многим) позволяет реализовать связь "Многие ко Многим"(many-to-many multiplicity). Рассмотрим пример:
В данном примере покупатель Customer связан с коллекцией штатов/государств states, но также State может быть связан с несколькими покупателями (customers).
Методы (Methods)
Методы определяются как обычно. Класс OpenXava-сущность (на самом деле, JPA entity) - это самый обычный класс Java. Пример:Методы - это основа объектнов, т.к. без них объект всего лишь обёртка данных. Мы рекомендуем всегда, когда это возможно, реализовывать бизнес-логику внутри методов (model layer), а не действий (controller layer).
Методы для поиска в БД (Finders)
Метод для поиска в БД - это узкоспециализированный статический метод, предназначенный для поиска одного объекта или коллекции экземпляров объектов, удовлетворяющих определенным критериям.Несколько примеров:
Теперь несколько примеров использования методов для поиска:
Обращаем ваше внимание, что использование методов для поиска позволяет улучшить читаемость создаваемого кода в сравнении с многословными запросами к API стандарта JPA. Но это всего лишь рекомендация к стилю программирования, никто не запрещает вам напрямую использовать запросы к JPA.
Проверка для перечислимого типа (Entity validator)
Аннотация @EntityValidator предназначена для реализации проверок на уровне сущности (model level). Данная аннотация должна использоваться для общих проверок - когда выполняется валидация несколько свойств одновременно и данные проверки не могут быть логически соотносены к конкретным свойствам.Синтаксис:
- value (обязательный аттрибут): Класс, который реализует логику проверки (класс-валидатор). Данный класс должен реализовывать интерфейс IValidator.
- onlyOnCreate (необязательный аттрибут): Если данный аттррибут равен true, то проверка будет выполняться только в момент создания нового объекта. но не будет выполнятся в момент изменения уже существующего объекта. Значение по-умолчанию false.
- properties (несколько @PropertyValue, необязательный аттрибут): Предоставляет возможность установить значения свойств-параметров вызова класса-валидатора.
Пример:Код класса-валидатора для примера выше:
Класс-валидатор должен реализовывать интерфейс IValidator (1), что принуждает к реализации метода validate(Messages messages) (2). В данном методе выполняются проверки, если проверки не проходят (выполняются с ошибкой), то вызывается errors.add(..) (3), используя идентификаторы сообщений из ресурсного фалйаi18n и параметров для подстановки в сообщение. Если процесс валидации выполняется с ошибкой (данный процесс сопровождается вызовом всех зарегистрированных валидаторов), то OpenXava не сохраняет объект и выводит сообщения об ошибках пользователю (созданные внутри метода validate()).
В нашем примере выполняется общая проверка для свойств description и unitPrice. В таких случаях рекомендуется выполнять проверку на уровне сущности (model level).
Для одной сущности можно реализовать несколько @EntityValidators. Пример:
@EntityValidator определен как Hibernate Validator contraint (начиная с версии v3.0.1).
Проверка при удалении (Remove validator)
Аннотация @RemoveValidator также реализован на уровне сущности (model level) и предназначен для выполнения проверки перед удалением объекта. Данная проверка позволяет запретить удаление объекта.Синтаксис:
- class (обязательный аттрибут): Класс, реализующий логику проверки. Должен реализовывать интерфейс IRemoveValidator.
- properties (несколько определений @PropertyValue, необязательный аттрибут): Используется для передачи параметров валидатору, перед его выполнением.
Пример использования такого валидатора:Пример кода самого валидатора:
Класс-валидатор должен реализовывать интерфейс IRemoveValidator (1) , что принуждает реализовывать метод setEntity() (2), который получает объект для удаления. Если при выполнении метода validate() (3) в объект Messages будет добавлено одно или более сообщение об ошибке, то считается, что проверка выполнилась с ошибкой. Если после выполнения всех проверок есть хотя бы одна ошибка валидации, то OpenXava не удаляет объект и выводит сообщения с ошибками валидации пользователю.
В нашем примере валидатор проверяет, есть ли доставки (deliveries), которые используют данный тип доставки (delivery type) прежде, чем удалить данный тип доставки.
Также как в случае с @EntityValidator, можно использовать несколько @RemoveValidator для сущности, обернув их аннтоацией @RemoveValidators.
@RemoveValidator выполняется в момент, когда пользователь пытается удалить сущность средствами OpenXava (использование MapFacade или стандартных действий (actions) OX), но не будет выполняться, если вы попытаетесь напрямую использоваться вызовы JPA. При прямом использовании JPA для создания констрейна на удаление используйте аннотацию @PreRemove JPA.
Методы обратного вызова JPA (JPA callback methods)
@PrePersist - контролируемая вставка данных в БД
Используя аннотацию @PrePersist, вы можете встроить свою собственную логику в процесс выполнения сохранения объекта в БД.Пример:
В нашем примере, каждый раз при создании сущности DeliveryType в свойство description добавляется суффикс.
@PreUpdate - контролируемое изменение данных в БД
Используя аннотацию @PreUpdate, вы можете встроить свою собственную логику, которая будет выполняться после перехода сущности в определенное состояние или перед тем, как сохранять изменения сущности в БД, т.е., просто перед выполнением выражения UPDATE в БД.Пример:
В этом примере при люьбом изменении DeliveryType в описание добавляется суффикс.
Как вы наверное уже убедились, логика использования @PreUpdate аналогична @PrePersist, разница только в моменте, когда соответствующий класс вызывается.
Другие методы обратного вызова JPA
Аналогично выше описанным аннотациям вы можете использовать остальные методы обратного вызова, определенные в стандарте JPA: @PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate, @PostUpdate and @PostLoad.Встраиваемые классы (Embeddable classes)
Как сказано в спецификации JPA:"Сущность может использовать другие хорошо спроектированные классы для отображения состояния сущности. Экземпляры данных классов, в отличие от собственно экземпляров сущности, не должны самостоятельно отображать сохраняемую сущность (do not have persistent identity). В противоположность, они существуют только как встраиваемые объекты той сущности, к которой они относятся. Такие встраиваемые объекты должны строго принадлежат сущности-владельцу и не должны использоваться несколькими сохраняемыми сущностями (are not sharable across persistent entities)."
Оригинальный текст:
("An entity may use other fine-grained classes to represent entity state. Instances of these classes, unlike
entity instances themselves, do not have persistent identity. Instead, they exist only as embedded objects
of the entity to which they belong. Such embedded objects belong strictly to their owning entity, and are
not sharable across persistent entities.")
Синтраксис встраиваемого класса:
Встроенная ссылка (Embedded reference)
Возможно использование встраиваемых свойств-ссылок.В рассматриваемом далее примере определяется класс Address (аннотированный с помощью @Embedded), на который ссылается основной класс-сущность. В основной сущности встроенная ссылка определяется следующим образом:
Сам класс Address необходимо определить как встраиваемый:
Как вы видете, встраиваемый класс может реализовывать интерфейс (1), содержать свойства-ссылки (2) и т.д., но не может использовать сохранямые коллекции или вызывть методы обратного вызова JPA.
Использовать объект Address можно следующим образом (если нам необходимо прочитать его):
Другой вариант работы с объектом Address:
В нашем примере мы использовали простую ссылку на Address (не коллекцию), и генерироваться будет код простого JavaBean, чей жизненный цикл будет ассоцирован с объектом-контейнером. Т.е. Address добавляется и удаляется только через контейнер Customer. Еще раз обращаем ваше внимание на то, что встраиваемый класс Address не имеет своего жизненного цикла и не может разделяться между несколькими (использоваться другими) Customer.
Встраиваемые коллекции (Embedded collections)
Встраиваемые коллекции не поддерживаются JPA 1.0. Тем не менее, разработчик сам может реализовать их, используя коллекции сущностей и каскадные типы (cascade type) REMOVE или ALL. OpenXava обрабатывает такие коллекции специальным способом так, как если бы они были встраиваемыми коллекциями.Рассмотрим выше сказанное на примере. В сущности-контейнере (в нашем примере, это Invoice) необходимо определить следующую коллекцию:
Обращаем ваше внимание на каскадный тип CascadeType.REMOVE и на то, что InvoiceDetail - это сущность, а не встраиваемый класс:
В примере был представлен класс, описывающий сложную сущность, с калькуляторами, валидаторами, ссылками и прочие. Кроме этого вы можете здесь определить ссылку на класс-контейнер (invoice). Если вы это сделаете это, то при удалении Invoice будут удаляться все InvoiceDetail. Также наличие такой ссылки влияет на генерируемый пользовательский интерфейс (более подробную информацию вы найдете в главе Представление).
Наследование (Inheritance)
OpenXava поддерживает наследование Java и наследование JPA.Рассмотрим пример. Мы можем использовать @MappedSuperclass следующим образом:
Можно использовать несколько уровней наследования. Т.к. можно определить аттрибут @MappedSuperclass для класса, который уже наследует другой класс:
Теперь мы можем использовать оба класса (и Identifiable, и Nameable), чтобы определять экземпляры:
Наконец определим рельную сущность на базе выше перечисленных классов:
Совет: Вы можете создать модуль OpenXava для Human (Человке) и Programmer (Программист), а не для классов Identifiable или Nameble напрямую). В модуле Programmer module пользователь получить доступ только к "программистам". А в модуле Human пользователь сможет работать со всеми объектами Human и Programmer. Более того, когда пользователь попытается просмотреть карточку "программиста" из модуля Human module, именно представление Programmer будет отображено. Это то, что я называю, настоящим полиформизмом.
Остановимся на проецировании - аннотация @AttributeOverrides поддерживается, но, на настоящий момент, только стратегия иерархии "table" = "class" работает корректно.
Составной ключ (Composite key)
Предпочтительный способ определения ключа в таблице (в сущности) - это простой автогенерируемый ключ (почитайте про аннотации @Id и @GeneratedValue), но иногда, чаще всего, когда вы работаете с уже существующей базой данных, на структуру которой вы не можете повлиять, вам небходимо проецировать сущность в таблицу, в которой ключ состоит из нескольких стобцов. Данный случай может быть реализован в JPA (и, соответственно, в OpenXava) двумя путями: используя одну из аннтоаций @IdClass или @EmbeddedIdКласс Id (Id class)
В этом подходе аннотация @IdClass внутри вашей сущности указывает на ключевой класс. В этом случае свойства-части ключа описываются прямо в вашей сущности, при этом вам необходимо промаркировать их аннотацией @Id:Затем вам необходимо определить ваш id-класс, который представляет собой обычный сериализирумый (serializable) класс, который содержит все ключевые свойства вашей сущности-контейнера:
Встраиваемый Id (Embedded id)
Чтобы использовать встраеваемый Id, необходимо использовать аннотацию @EmbeddedId. Определение же такого класса делается аннотацией @Embeddable . В отличие от использования @IdClass вам не нужно описывать свойства-части ключа в основной сущности.Рассмотрим сам встраиваемый класс, который содержит ключевые свойства:
Стандартный метод проверки Hibernate (Hibernate Validator) (начиная с v3.0.1)
OpenXava полность поддерживает возможности Hibernate Validator. Вы можете создавать свои собственные ограничения (constrains) для ваших сушностей (обратитесь к документации Hibernate Validator documention за более подробной информацией) и OpenXava расспознает их и будет выводить соответствующие сообщения, если валидация не пройдет, пользователю.Более того, аннотации OpenXava @Required, @PropertyValidator и @EntityValidator сами по себе определены как Hibernate Validator constraints, что означает, что если вы будет использовать прямоц доступ к JPA, данные валидации будут корректно работать.
С другой стороны аннотации @RemoveValidator, @PropertyValidator(onlyOnCreate=true), EntityValidator(onlyOnCreate=true) и Метод проверки исходного значения реализованы только в OpenXava и не распознаются Hibernate Validator или JPA.